/**
 * Code template comes from Eclipse plug-in "Bytecode"
 */
package com.googlecode.cswish.struts.spring.generator;

import static com.googlecode.cswish.asm.Opcodes.ACC_ABSTRACT;
import static com.googlecode.cswish.asm.Opcodes.ACC_INTERFACE;
import static com.googlecode.cswish.asm.Opcodes.ACC_PUBLIC;
import static com.googlecode.cswish.asm.Opcodes.V1_6;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.googlecode.cswish.asm.ClassWriter;
import com.googlecode.cswish.asm.MethodVisitor;
import com.googlecode.cswish.asm.Type;
import com.googlecode.cswish.struts.spring.NameType;

/**	 
 * Create the interface of the value object
 * 
 * @author Jerry.feng Date: 10/09/2008
 */
public class ValueObjectIntfHelper {
	
	private static final Logger logger = Logger.getLogger(ValueObjectIntfHelper.class);
	
	protected static String getSign(final Type returnType,
			final String genericReturnType, final Type[] argumentTypes,
			final String[] genericArgumentTypes) {
		if (genericReturnType == null && genericArgumentTypes == null) {
			return null;
		}
		
		return getGenericMethodDescriptor(returnType, genericReturnType, argumentTypes, genericArgumentTypes);
	}
	
	private static String getGenericMethodDescriptor(final Type returnType,
			final String genericReturnType, final Type[] argumentTypes,
			final String[] genericArgumentTypes) {
		StringBuilder buf = new StringBuilder();
		buf.append('(');
		if (argumentTypes != null) {
			for (int i = 0; i < argumentTypes.length; ++i) {
				buf.append(getGenericDescriptor(argumentTypes[i], genericArgumentTypes[i]));
			}
		}
		buf.append(')');
		buf.append(getGenericDescriptor(returnType, genericReturnType));		
		return buf.toString();
	}
	
	private static String getGenericDescriptor(Type type, String genericType) {
		if (genericType == null) {
			return type.getDescriptor();
		} else {
			String desc = type.getDescriptor();
			String replaced = genericType.replace('.', '/');
			String genericTypeDesc;
			if (replaced != genericType) {	// it's a object
				genericTypeDesc = 'L' + replaced + ';';
			} else {
				genericTypeDesc = genericType;
			}	
			return desc.substring(0, desc.length() - 1) + '<' + genericTypeDesc + ">;";
		}
	}
	
	public static byte[] defineVOIntf(String name, NameType[] properties) {
		// create a new one
		ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
		cw.visit(V1_6, ACC_PUBLIC + ACC_ABSTRACT
				+ ACC_INTERFACE,
				name.replace('.', '/'),		// class name
		    null, 							// 
		    "java/lang/Object",				// super class
		    null);							// interfaces
		
		if (properties != null && properties.length > 0) {
			for (NameType field : properties) {
				String capName = StringUtils.capitalize(field.name);
				String getMethodName = "get" + capName;
				String typeDesc = Type.getDescriptor(field.type);
				
				Type returnType = Type.getTypeByDesc(typeDesc);
				Type[] parameterTypes = new Type[] {};
				String getMethodDesc = Type.getMethodDescriptor(
						returnType,parameterTypes);
				String getSignDesc = getSign(returnType, field.genericType, null, null);
				String setMethodName = "set" + capName;
				returnType = Type.getType(Void.TYPE);
				parameterTypes = new Type[] {Type.getTypeByDesc(typeDesc)};
				String setMethodDesc = Type.getMethodDescriptor(
						returnType, parameterTypes);
				String[] genericArgumentTypes = null;
				if (field.genericType != null) {
					genericArgumentTypes = new String[] {field.genericType};
				}
				String setSignDesc = getSign(returnType, null, parameterTypes, genericArgumentTypes);
				
				MethodVisitor mv;
				// read method
				mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, 
					getMethodName,			// method name
				    getMethodDesc,			// method descriptor
				    getSignDesc,			// method attributes
				    null);					// exceptions
				mv.visitEnd();
				
				// write method
				mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, 
						setMethodName,		// method name
						setMethodDesc,		// method descriptor
						setSignDesc,		// method attributes
					    null);				// exceptions
				mv.visitEnd();
			}
		}
		
		cw.visitEnd();
		byte[] bytecode = cw.toByteArray();
		return bytecode;
	}
}
